/**
 * Class: Dot2D
 *
 * Purpose: 
 *
 * Created: Jan 27, 2010 @ 9:37:10 AM
 * @author Ken Scott
 */
package org.openfantasia.ps.shape;

import org.openfantasia.ps.actor.Actor;
import org.openfantasia.ps.modifier.Behavior;
import org.openfantasia.ps.particle.Particle;
import org.openfantasia.ui.PColor;
import org.openfantasia.ui.PFrame;
import org.openfantasia.util.math.DPoint3;

public class Shape extends Actor {

	protected boolean visible = true;

	protected boolean locationOnly = false;

	protected DPoint3 size = new DPoint3(1, 1, 1);

	protected double lineWeight = 1;

	protected double opacity = 1;

	protected int detail = 8;

	protected DPoint3 position = new DPoint3();

	protected DPoint3 orientation = new DPoint3();

	protected PColor lineColor = null;

	protected PColor fillColor = null;

	protected Scribe scribe = null;


	public Shape(PFrame frame) {
		super(frame);
	}


	public Shape(PFrame frame, DPoint3 size) {
		super(frame);
		this.size = size;
	}


	public Shape(PFrame frame, DPoint3 size, double lineWeight) {
		super(frame);
		this.size = size;
		this.lineWeight = lineWeight;
	}


	public Shape(Shape that) {
		copy(that);
	}


	public void copy(Shape that) {
		super.copy(that);
		this.visible = that.visible;
		this.locationOnly = that.locationOnly;
		this.size = that.size.copy();
		this.lineWeight = that.lineWeight;
		this.opacity = that.opacity;
		this.detail = that.detail;
		this.position = that.position.copy();
		this.orientation = that.orientation.copy();
		this.lineColor = that.lineColor;
		this.fillColor = that.fillColor;
		this.scribe = that.scribe;
	}


	public boolean isVisible() {
		return visible;
	}


	public boolean isVisible(Particle particle) {
		Boolean visible = (Boolean) getBehaviorValue(Behavior.VISIBILITY, particle, getTime(), isVisible());
		return particle.isVisible() && visible;
	}


	public void setVisible(boolean visible) {
		this.visible = visible;
	}


	public boolean isLocationOnly() {
		return locationOnly;
	}


	public void setLocationOnly(boolean locationOnly) {
		this.locationOnly = locationOnly;
	}


	public DPoint3 getPosition() {
		return position;
	}


	public DPoint3 getPosition(Particle particle) {
		DPoint3 offset = (DPoint3) getBehaviorValue(Behavior.POSITION, particle, getTime(), getPosition().copy());
		return particle.getPosition().copy().add(offset);
	}


	public void setPosition(DPoint3 position) {
		this.position = position;
	}


	public double getLineWeight() {
		return lineWeight;
	}


	public double getLineWeight(Particle particle) {
		double w = (Double) getBehaviorValue(Behavior.LINE_WEIGHT, particle, getTime(), getLineWeight());
		return particle.getLineWeight() * w;
	}


	public void setLineWeight(double lineWeight) {
		this.lineWeight = lineWeight;
	}


	public double getOpacity() {
		return opacity;
	}


	public double getOpacity(Particle particle) {
		double w = (Double) getBehaviorValue(Behavior.OPACITY, particle, getTime(), getOpacity());
		return particle.getOpacity() * w;
	}


	public void setOpacity(double opacity) {
		this.opacity = opacity;
	}


	public DPoint3 getSize() {
		return size;
	}


	public DPoint3 getSize(Particle particle) {
		DPoint3 multiplier = (DPoint3) getBehaviorValue(Behavior.SIZE, particle, getTime(), getSize().copy());
		return particle.getSize().copy().mul(multiplier);
	}


	public void setSize(DPoint3 size) {
		this.size = size.copy();
	}


	public int getDetail() {
		return detail;
	}


	public void setDetail(int detail) {
		this.detail = detail;
	}


	public DPoint3 getOrientation() {
		return orientation;
	}


	public DPoint3 getOrientation(Particle particle) {
		DPoint3 offset = (DPoint3) getBehaviorValue(Behavior.ORIENTATION, particle, getTime(), getOrientation().copy());
		return particle.getOrientation().copy().add(offset);
	}


	public void setOrientation(DPoint3 orientation) {
		this.orientation = orientation.copy();
	}


	public PColor getLineColor() {
		return lineColor;
	}


	public PColor getLineColor(Particle particle) {
		PColor result = (PColor) getBehaviorValue(Behavior.LINE_COLOR, particle, getTime(), getLineColor());

		PColor pColor = particle.getLineColor();
		if (pColor != null) {
			result = (PColor) pColor.copy().mul(result);
		}
		else if (result == null) {
			result = pColor;
		}

		double op = getOpacity(particle);
		if ((op != 1.0) && (result != null)) {
			result = result.opacify((float) op);
		}

		return result;
	}


	public void setLineColor(PColor lineColor) {
		this.lineColor = lineColor;
	}


	public PColor getFillColor() {
		return fillColor;
	}


	public PColor getFillColor(Particle particle) {
		PColor result = (PColor) getBehaviorValue(Behavior.FILL_COLOR, particle, getTime(), getFillColor());

		PColor pColor = particle.getFillColor();
		if (pColor != null) {
			result = (PColor) pColor.copy().mul(result);
		}
		else if (result == null) {
			result = pColor;
		}

		double op = getOpacity(particle);
		if ((op != 1.0) && (result != null)) {
			result = result.opacify((float) op);
		}

		return result;
	}


	public void setFillColor(PColor fillColor) {
		this.fillColor = fillColor;
	}


	public Scribe getScribe() {
		return scribe;
	}


	public void setScribe(Scribe scribe) {
		this.scribe = scribe;
	}


	public void draw(Particle particle) {
		if (isVisible(particle)) {
			frame.pushStyle();
			frame.noFill();
			frame.noStroke();

			frame.pushMatrix();
			frame.translate(getPosition(particle));
			frame.scale(getSize(particle));
			frame.rotate(getOrientation(particle));

			if (locationOnly) {
				frame.stroke(PColor.WHITE);
				frame.point(DPoint3.ORIGIN);
			}
			else {
				PColor fillC = getFillColor(particle);
				if (fillC != null) {
					frame.fill(fillC);
				}

				PColor lineC = getLineColor(particle);
				if (lineC != null) {
					frame.stroke(lineC);
				}

				double weight = getLineWeight(particle);
				if (weight > 0) {
					frame.strokeWeight(weight);
				}

				if (scribe != null) {
					scribe.draw(frame, particle, this);
				}
				else {
					drawShape(particle);
				}
			}

			frame.popMatrix();
			frame.popStyle();
		}
	}


	public void drawShape(Particle particle) {
		throw new RuntimeException("Reached Shape.drawShape() default method");
	}


	public abstract static class Scribe {

		public abstract void draw(PFrame frame, Particle particle, Shape shape);
	}
}